/*
 * @SuperTV All Rights Reserved
 */

package com.android.volley.toolbox;

import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;

import com.android.volley.AuthFailureError;
import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.NetworkError;
import com.android.volley.NetworkResponse;
import com.android.volley.NoConnectionError;
import com.android.volley.Request;
import com.android.volley.ResponseDelivery;
import com.android.volley.RetryPolicy;
import com.android.volley.ServerError;
import com.android.volley.TimeoutError;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.huyn.baseframework.utils.Constant;
import com.huyn.baseframework.utils.DateUtil;
import com.huyn.baseframework.utils.StorageUtil;
import com.huyn.baseframework.utils.StringUtils;
import com.huyn.baseframework.utils.Utils;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.cookie.DateUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.zip.GZIPInputStream;

/**
 * A network performing Volley requests over an {@link HttpStack}.
 */
public class BasicNetwork implements Network {
    protected static final boolean DEBUG = VolleyLog.DEBUG;

    private static int SLOW_REQUEST_THRESHOLD_MS = 3000;

    private static int DEFAULT_POOL_SIZE = 4096;

    protected final HttpStack mHttpStack;

    protected final ByteArrayPool mPool;
    
    private boolean isFirst = true;

    private ResponseDelivery mDelivery;

    /**
     * @param httpStack HTTP stack to be used
     */
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }

    private void logParams(Request<?> request) {
    	if(!Constant.DEBUG)
    		return;
		try {
	    	StringBuffer sb = new StringBuffer();
            String url = request.getUrl();
			sb.append(url).append(url.endsWith("?") ? "" : "?");
			Map<String, String> postdata = request.getParams();
			
			if(postdata == null)
				return;
			
//			Utils.Log("URL_METHOD", postdata.get(OpenApi.API_METHOD)== null?"":postdata.get(OpenApi.API_METHOD).toString());;
		    
			for (Map.Entry<String, String> item : postdata.entrySet()) {
				sb.append("").append(item.getKey()).append("=").append(item.getValue()).append("&");
//				Utils.Log("URL_PARAM", item.getKey()+"="+item.getValue());
			}
			Utils.Log("URL", sb.toString());
    	} catch(Exception e) {
    		e.printStackTrace();
    	}
	 }
    
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = new HashMap<String, String>();
            try {
            	// Log Params
            	logParams(request);
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();

                headers.put("Accept-Encoding", "gzip");
                headers.put("Connection" , "Keep-Alive");
                
                addCacheHeaders(headers, request.getCacheEntry());
                
                httpResponse = mHttpStack.performRequest(request, headers);

                if(Constant.IMG_DEBUG && request instanceof ImageRequest)
                    Log.i("VOLLEYIMG", "no cache, do img request:" + request.getCacheKey());

                //readresponse end
                request.timeReadDataEnd = System.currentTimeMillis();
                
                if(StringUtils.isNotBlank(request.getApiName())) {
	                if(isFirst){
	        			setSynGewaraTime(httpResponse, request.getApiName());
	        			isFirst = false;
	        		}else{
	        			setAsynGewaraTime(httpResponse, request.getApiName());
	        		}
                }
                
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry().data, responseHeaders, true);
                }

//                responseContents = entityToBytes(httpResponse.getEntity());
                boolean storeToLocal = false;
                if(request instanceof FileRequest)
                    responseContents = entityToFile(httpResponse, (FileRequest) request);
                else if(request instanceof SuperImageRequest) {
                    storeToLocal = true;
                    responseContents = entityToFile(httpResponse, request);
                } else
                    responseContents = entityToBytes(httpResponse);

                //data size
                request.responseDataSize = responseContents.length;
                
                //print logs
                if(Constant.IMG_DEBUG) {
                	if(request instanceof ImageRequest) {
                		Utils.Log("VOLLEYREQUEST", "load " + request.getCacheKey() + " data size:" + request.responseDataSize + " in " + (request.timeReadDataEnd - request.timeStartRequest));
                	}
                }
                
                /**
                 * 如果出错
                 */
                if(null == responseContents)
                	return null;
                
                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false, storeToLocal);
            } catch (SocketTimeoutException e) {
            	e.printStackTrace();
            	if(Constant.DEBUG) {
                	Utils.Log("VOLLEYREQUEST", "response from " + request.getApiName() + " sockettimeout");
                	
//                	LogDisplayerUtil.log("response from " + request.getApiName() + " sockettimeout");
                }
            	if(!request.allowRetry())
            		return null;
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
            	e.printStackTrace();
            	if(Constant.DEBUG) {
                	Utils.Log("VOLLEYREQUEST", "response from " + request.getApiName() + " connecttimeout");

//                	LogDisplayerUtil.log("response from " + request.getApiName() + " connecttimeout");
                }
            	if(!request.allowRetry())
            		return null;
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
            	e.printStackTrace();
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
            	e.printStackTrace();
            	if(!request.allowRetry())
            		return null;
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else {
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(networkResponse);
                }
            } catch (Exception e) {
            	e.printStackTrace();
            	return null;
			}
        }
    }

    @Override
    public void attachDelivery(ResponseDelivery delivery) {
        this.mDelivery = delivery;
    }

    /**
     * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
     */
    private void logSlowRequests(long requestLifetime, Request<?> request,
            byte[] responseContents, StatusLine statusLine) {
        if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
            VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
                            "[rc=%d], [retryCount=%s]", request, requestLifetime,
                    responseContents != null ? responseContents.length : "null",
                    statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
        }
    }

    /**
     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
     * request's retry policy, a timeout exception is thrown.
     * @param request The request to use.
     */
    private static void attemptRetryOnException(String logPrefix, Request<?> request,
            VolleyError exception) throws VolleyError {
    	RetryPolicy retryPolicy = request.getRetryPolicy();
        int oldTimeout = request.getTimeoutMs();

        try {
            retryPolicy.retry(exception);
        } catch (VolleyError e) {
            request.addMarker(
                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
            throw e;
        }
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }

    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }

        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }

        if (entry.serverDate > 0) {
            Date refTime = new Date(entry.serverDate);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }

    protected void logError(String what, String url, long start) {
        long now = SystemClock.elapsedRealtime();
        VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
    }

    public static File getCacheDir(String path){
        return getCacheDir(path, false);
    }

    public static File getCacheDir(String path, boolean canUseSDCard){
        File file;
        try{
            if(canUseSDCard)
                file = new File(Environment.getExternalStorageDirectory(), path);
            else
                file = new File(StorageUtil.cachePath, path);
        } catch(Exception e1) {
            try {
                file = new File(StorageUtil.cachePath, path);
            } catch (Exception e2) {
                throw e2;
            }
        }

        if(null != file && !file.exists()) {
            if(!file.mkdirs()) {
                throw new RuntimeException("can't make dirs "
                        + file.getAbsolutePath());
            }
        }
        return file;
    }

    public static File newPluginFile(String fileName) {
        return new File(getCacheDir(Constant.getPluginDir()), fileName);
    }

    public static File newPluginFile(String fileName, boolean canUseSDCard) {
        return new File(getCacheDir(Constant.getPluginDir(), canUseSDCard), fileName);
    }

    public static File newImgFile(String fileName) {
        return new File(getCacheDir(Constant.CACHE_CACHE), fileName.hashCode() + "");
    }


    /**
     * @param httpResponse
     * @param request
     * @return
     * @throws IOException
     * @throws ServerError
     */
    private byte[] entityToFile(HttpResponse httpResponse, Request request) throws IOException, ServerError {
        File file = newImgFile(request.getUrl());

        File tempFile = new File(getCacheDir(Constant.GEWARA_TEMP), request.getUrl().hashCode() + "");

        return entityToFile(httpResponse, request, file, tempFile);
    }

    /**
     * @param httpResponse
     * @param request
     * @return
     * @throws IOException
     * @throws ServerError
     */
    private byte[] entityToFile(HttpResponse httpResponse, FileRequest request) throws IOException, ServerError {
        String fileName = request.getFileTitle();

        File file = newPluginFile(fileName, request.canUseSDCard());
        File tempFile = new File(getCacheDir(Constant.GEWARA_TEMP, request.canUseSDCard()), StringUtils.isNotBlank(fileName) ? fileName : (request.getUrl().hashCode() + ""));

        return entityToFile(httpResponse, request, file, tempFile);
    }

    private byte[] entityToFile(HttpResponse httpResponse, Request request, File file, File tempFile) throws IOException, ServerError {
        HttpEntity entity = httpResponse.getEntity();
        FileOutputStream out = new FileOutputStream(tempFile);

        FileInputStream fosfrom = null;
        FileOutputStream fosto = null;
        try {
            Header contentEncoding = httpResponse.getFirstHeader("Content-Encoding");
            InputStream in = entity.getContent();
            int contentLength = (int) entity.getContentLength();
            if (contentEncoding!=null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
                in = new GZIPInputStream(in);
            }
            if (in == null) {
                throw new ServerError();
            }

            byte[] buffer = new byte[1024];

            int count;
            int progress = 0;

            String dir = file.getAbsolutePath();

            while ((count = in.read(buffer)) != -1) {
                progress += count;
                if(mDelivery != null)
                    mDelivery.postProgress(request, progress*1.0f/contentLength);
                out.write(buffer, 0, count);
            }

            //移动文件到缓存
            fosfrom = new FileInputStream(tempFile);
            fosto = new FileOutputStream(file);

            while ((count = fosfrom.read(buffer)) > 0) {
                fosto.write(buffer, 0, count);
            }

            return dir.getBytes();
        } catch(Exception e) {
            e.printStackTrace();
            System.gc();
            return null;
        } catch(OutOfMemoryError e) {
            e.printStackTrace();
            System.gc();
            return null;
        } finally {
            try {
                // Close the InputStream and release the resources by "consuming the content".
                entity.consumeContent();

                if (out != null) {
                    out.flush();
                    out.close();
                }

                if(fosfrom != null) {
                    fosfrom.close();
                }

                if(fosto != null) {
                    fosto.flush();
                    fosto.close();
                }

                tempFile.delete();
            } catch (Exception e) {
                // This can happen if there was an exception above that left the entity in
                // an invalid state.
                VolleyLog.v("Error occured when calling consumingContent");
            }
        }
    }

    /** Reads the contents of HttpEntity into a byte[]. */
    private byte[] entityToBytes(HttpResponse httpResponse) throws IOException, ServerError {
//    private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
        HttpEntity entity = httpResponse.getEntity();
    	PoolingByteArrayOutputStream bytes = null;
        byte[] buffer = null;
        try {
        	bytes = new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
//            InputStream in = entity.getContent();
        	Header contentEncoding = httpResponse.getFirstHeader("Content-Encoding");
			InputStream in = entity.getContent();
			if (contentEncoding!=null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
				in = new GZIPInputStream(in);
			}
            if (in == null) {
                throw new ServerError();
            }
            buffer = mPool.getBuf(1024);
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } catch(OutOfMemoryError e) {
        	e.printStackTrace();
        	System.gc();
        	return null;
        } finally {
            try {
                // Close the InputStream and release the resources by "consuming the content".
                entity.consumeContent();
                
                if(mPool != null)
                	mPool.returnBuf(buffer);
                
                if(bytes != null)
                	bytes.close();
            } catch (Exception e) {
                // This can happen if there was an exception above that left the entity in
                // an invalid state.
                VolleyLog.v("Error occured when calling consumingContent");
            }
//        	mPool.returnBuf(buffer);
//        	bytes.close();
        }
    }

    /**
     * Converts Headers[] to Map<String, String>.
     */
    private static Map<String, String> convertHeaders(Header[] headers) {
        Map<String, String> result = new HashMap<String, String>();
        for (int i = 0; i < headers.length; i++) {
            result.put(headers[i].getName(), headers[i].getValue());
        }
        return result;
    }
    
    private void setSynGewaraTime(HttpResponse httpResponse, final String apiName) {
		try{
			Header[] headers = httpResponse.getHeaders("Date");
			if(headers == null || headers.length == 0)
				return;
			String strDate = headers[0].toString();
			strDate = strDate.replace("Date: ", "");
			Date d2 = null;
			SimpleDateFormat sdf=new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",Locale.ENGLISH);
			d2=sdf.parse(strDate); 
			int tz = d2.getTimezoneOffset();
			int offset = (tz + 8*60)*60*1000;
			long offtime = d2.getTime() + offset;

			SimpleDateFormat formatter =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//			System.out.println("api:" + apiName + "============get serviceTime:" + formatter.format(new Date(offtime)));
			
			DateUtil.setGewaraTime(offtime);
			DateUtil.setLocalTime(System.currentTimeMillis());
			Utils.Log("GEWARA_TIMER", "格瓦拉时间："+ DateUtil.getCurTimeStr());
		}catch (Exception e) {
			DateUtil.setGewaraTime(System.currentTimeMillis());
			DateUtil.setLocalTime(System.currentTimeMillis());
		}   
	}
	
	private void setAsynGewaraTime(HttpResponse httpResponse, final String apiName) {
		final HttpResponse httpR = httpResponse;
		new Thread(new Runnable() { 
			@Override
			public void run() {
				try{
					Header[] headers = httpR.getHeaders("Date");
					if(headers == null || headers.length == 0)
						return;
					String strDate = headers[0].toString();
					strDate = strDate.replace("Date: ", "");
					Date d2 = null;
					
					SimpleDateFormat sdf=new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",Locale.ENGLISH);
					d2=sdf.parse(strDate); 
					int tz = d2.getTimezoneOffset();
					int offset = (tz + 8*60)*60*1000;
					long offtime = d2.getTime() + offset;

					SimpleDateFormat formatter =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//					System.out.println("api:" + apiName + "============get serviceTime:" + formatter.format(new Date(offtime)));
					
				  	DateUtil.setGewaraTime(offtime);
				  	DateUtil.setLocalTime(System.currentTimeMillis());
//				  	Utils.Log("GEWARA_TIMER", "格瓦拉时间："+DateUtil.getCurTimeStr());
				}catch (Exception e) {
				}   
			}
		}).start();
	}
    
}
